#!/usr/bin/python2.7
#
# dedupv1 - iSCSI based Deduplication System for Linux
#
# (C) 2008 Dirk Meister
# (C) 2009 - 2011, Dirk Meister, Paderborn Center for Parallel Computing
# (C) 2012 Dirk Meister, Johannes Gutenberg University Mainz
#
# This file is part of dedupv1.
#
# dedupv1 is free software: you can redistribute it and/or modify it under the terms of the
# GNU General Public License as published by the Free Software Foundation, either version 3
# of the License, or (at your option) any later version.
#
# dedupv1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
# even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with dedupv1. If not, see http://www.gnu.org/licenses/.
#

import sys
import os
import optparse
import json
if "DEDUPV1_ROOT" not in os.environ:
    DEDUPV1_ROOT= os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]),"../"))
    os.environ["DEDUPV1_ROOT"] = DEDUPV1_ROOT

sys.path.insert(0, os.path.normpath(os.path.join(DEDUPV1_ROOT, "lib/python")))
for file in [f for f in os.listdir(os.path.join(DEDUPV1_ROOT, "lib/python2.7/site-packages/")) if f.endswith(".egg")]:
    sys.path.insert(0, os.path.normpath(os.path.join(DEDUPV1_ROOT, "lib/python2.7/site-packages/", file)))
import dedupv1
from command import execute_cmd
import scst
from omdict import omdict
from monitor import Monitor
import iscsi_scst
import scst_user
import target
from dedupv1logging import log_error, log_info, log_warning, log_verbose
import config
from dedupv1_util import parse_params

monitor = None

def print_raw_data(data):
    def filter_data(d):
        if isinstance(d, dict):
            d2 = {}
            for (k,v) in d.items():
                if k != "secret":
                    d2[k] = filter_data(v)
            return d2
        elif isinstance(d, list):
            return [filter_data(e) for e in d]
        else:
            return d
    data = filter_data(data)
    print json.dumps(data, sort_keys=True, indent=4)

def addtotarget(options, args):
    config_params = omdict( [ ("op", "addtotarget") ] )
    config_params = parse_params(config_params, args)
    if not "name" in config_params:
        raise Exception("Missing required option: name");
    if not "target" in config_params:
        raise Exception("Missing required option: target");
    if len(config_params.get_multi("target")) != 1:
        raise Exception("Illegal option: target");

    user_name = config_params["name"]
    u = scst_user.User(user_name, config_params)
    validation_errors = u.validate()
    if len(validation_errors) > 0:
        raise Exception("Illegal user: " + ",".join(validation_errors))

    u = scst_user.read_user(monitor, user_name)
    for target_name in config_params.get_multi("target"):
        for user_target_name in u.targets():
            if target_name == user_target_name:
                if options.force:
                    log_warning(options, "User already assigned to target")
                else:
                    raise Exception("User already assigned to target")

    targets = target.read_all_targets(monitor)
    for target_name in config_params.get_multi("target"):
        for (tid, t) in targets.items():
            if target_name == t.name():
                break
        else:
            if options.force:
                log_warning(options, "Target not existing")
            else:
                raise Exception("Target not existing")

    monitor_data = monitor.read("user", config_params.items())
    if "ERROR" in monitor_data:
        raise Exception(monitor_data["ERROR"])
    else:
        if options.raw:
            print_raw_data(monitor_data)

        for target_name in config_params.get_multi("target"):
            t = target.read_target_by_name(monitor, target_name)
            iscsi_scst.add_user_to_target(u, t)

def rmfromtarget(options, args):
    config_params = omdict( [ ("op", "rmfromtarget") ] )
    config_params = parse_params(config_params, args)
    if not "name" in config_params:
        raise Exception("Missing required option: id");
    if not "target" in config_params:
        raise Exception("Missing required option: target");
    if len(config_params.get_multi("target")) != 1:
        raise Exception("Illegal option: target");

    user_name = config_params["name"]
    u = scst_user.read_user(monitor, user_name)
    validation_errors = u.validate()
    if len(validation_errors) > 0:
        raise Exception("Illegal user: " + ",".join(validation_errors))

    for target_name in config_params.get_multi("target"):
        if not target_name in u.targets():
            if options.force:
                log_warning(options, "Target not existing")
            else:
                raise Exception("Target not existing")

    monitor_data = monitor.read("user", config_params.items())
    if "ERROR" in monitor_data:
        raise Exception(monitor_data["ERROR"])
    else:
        if options.raw:
            print_raw_data(monitor_data)
        for target_name in config_params.get_multi("target"):
            t = target.read_target_by_name(monitor, target_name)
            iscsi_scst.rm_user_from_target(u, t)

def add_user(options, args):
    config_params = omdict( [ ("op", "add") ] )
    config_params = parse_params(config_params, args)

    if not "name" in config_params:
        raise Exception("Missing required option: name");
    if not "secret-hash" in config_params:
        raise Exception("Missing required option: secret-hash");

    user_name = config_params["name"]
    scst_user.check_user_name(user_name)

    u = scst_user.User(user_name, config_params)
    validation_errors = u.validate()
    if len(validation_errors) > 0:
        raise Exception("Illegal user: " + ",".join(validation_errors))

    targets = target.read_all_targets(monitor)
    for target_name in config_params.get_multi("target"):
        if not target_name in targets:
            if options.force:
                log_warning(options, "Target not existing")
            else:
                raise Exception("Target not existing")

    monitor_data = monitor.read("user", config_params.items());
    if "ERROR" in monitor_data:
        raise Exception(monitor_data["ERROR"])
    else:
        if options.raw:
            print_raw_data(monitor_data)
        u = scst_user.User(user_name, monitor_data[user_name])

        for target_name in config_params.get_multi("target"):
            t = target.read_target_by_name(monitor, target_name)
            iscsi_scst.add_user_to_target(u, t)
        if options.verbose:
            print "User %s registered" % u.name

def remove_user(options, args):
    config_params = omdict( [ ("op", "remove") ] )
    config_params = parse_params(config_params, args)
    if not "name" in config_params:
        raise Exception("Missing required option: name");

    u = scst_user.read_user(monitor, config_params["name"])
    if not u:
        raise Exception("User not existing")

    if len(u.targets()) > 0:
        raise Exception("Cannot delete user that is assigned to targets")

    monitor_data = monitor.read("user", config_params.items());
    if("ERROR" in monitor_data):
        raise Exception(monitor_data["ERROR"])
    else:
        if options.raw:
            print_raw_data(monitor_data)
        for target_name in u.targets():
            t = target.read_target_by_name(monitor, target_name)
            iscsi_scst.rm_user_from_target(u, t)
        log_verbose(options, "User %s unregistered" % u.name)

def change_user(options, args):
    config_params = omdict( [ ("op", "change") ] )
    config_params = parse_params(config_params, args)
    if not "name" in config_params:
        raise Exception("Missing required option: name");
    if not "secret-hash" in config_params:
        raise Exception("Missing required option: secret-hash");

    user_name = config_params["name"]
    u = scst_user.read_user(monitor, user_name)
    validation_errors = u.validate()
    if len(validation_errors) > 0:
        raise Exception("Illegal user: " + ",".join(validation_errors))

    u = scst_user.read_user(monitor, user_name)
    if not u:
        raise Exception("User not existing")

    monitor_data = monitor.read("user", config_params.items());
    if "ERROR" in monitor_data:
        raise Exception(monitor_data["ERROR"])
    else:
        if options.raw:
            print_raw_data(monitor_data)
        for target_name in u.targets():
            t = target.read_target_by_name(monitor, target_name)
            iscsi_scst.update_user_in_target(u, t)
        if options.verbose:
            print "Update user %s" % u.name

def show_users(options):
    if options.raw:
        print_raw_data(monitor.read("user"))
    else:
        users = scst_user.read_all_users(monitor)
        for name in sorted(users.keys()):
            u = users[name]
            print "User: %s" % name
            print "Targets: %s" % (", ".join(u.targets()))
            print

if __name__ == "__main__":
    usage = """usage: %prog (add | remove | target add | target remove | show) [options]

Examples:
%prog add name=admin1 secret-hash=secretpassword
%prog change name=admin1 secret-hash=anothersecret
%prog remove name=admin1
%prog target add name=admin1 target=iqn.2010-05.info.christmann:example
%prog target remove name=admin1 target=iqn.2010-05.info.christmann:example
%prog show

%prog --version
%prog --help

Create the secret-hash parameters using dedupv1_passwd
"""
    version = "%s (hash %s)" % (config.DEDUPV1_VERSION_STR, config.DEDUPV1_REVISION_STR)

    parser = optparse.OptionParser(usage=usage, version=version)
    parser.add_option("-p","--port", type="int", dest="port", help="port of the dedupv1d", default=config.DEDUPV1_DEFAULT_MONITOR_PORT)
    parser.add_option("--host", dest="hostname", help="hostname of the dedupv1d", default="localhost")
    parser.add_option("--raw", dest="raw", action="store_true", help="outputs in raw JSON format", default=False)
    parser.add_option("-f", "--force",
        action="store_true",
        dest="force",
        default=False)
    parser.add_option("-v", "--verbose",
        dest="verbose",
        action="store_true",
        default=False)
    parser.add_option("--debug",
        dest="debug",
        action="store_true",
        default=False)
    (options, args) = parser.parse_args()


    if not (dedupv1.check_root() or dedupv1.check_dedupv1_group()):
        print >> sys.stderr, "Permission denied"
        sys.exit(1)

    monitor = Monitor(options.hostname, options.port);

    if len(args) == 0:
        parser.error("No command specified")
        sys.exit(1)
    cmd = args[0]
    try:
        if cmd == "add":
            add_user(options, args[1:])
        elif cmd == "remove":
            remove_user(options, args[1:])
        elif cmd == "change":
            change_user(options, args[1:])
        elif cmd == "target":
            if len(args) == 1:
                parser.error("No sub command for command %s" % cmd)
            subcmd = args[1]
            if subcmd == "add":
                addtotarget(options, args[2:])
            elif subcmd == "remove":
                rmfromtarget(options, args[2:])
            else:
                parser.error("Illegal subcommand for command %s" % cmd)
        elif cmd == "show":
            show_users(options)
        else:
            parser.error("Invalid command %s" % cmd)
            sys.exit(1)
    except KeyboardInterrupt:
        sys.exit(1)
    except Exception as e:
        log_error(options, e)
        sys.exit(1)
    sys.exit(0)
